/*
 * Copyright (C) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.property

import jmmo.engine.event.Event

/**
 * An abstract event is the change of a certain [[jmmo.engine.property.Observable]].<br>
 * The property `chain` contains a sequence of containers through the event took place from end to beginning.<br>
 * When there is, some event fires its `chain` is empty. Then, when the event is sent out of the container, <br>
 * the container is added to the top of the `chain`. That is, <br>
 * container in which the event will be listed last, and the container from which the event got sent to listener first.<br>
 * `ChaneEvent` has derived classes to the specific type of change.<br>
 * [[jmmo.engine.property.Property]] generates the event `ChangePropertyEvent`.<br>
 * [[jmmo.engine.property.ObservableContainer]] generates events
 * `AddObsEvent` and `RemoveObsEvent` with self as node.<br>
 * Derived from `ChangeEvent` classes can be equal only to same type classes.
 * <p></p>
 * Абстрактное событие изменение некого [[jmmo.engine.property.Observable]].<br>
 * Свойство `chain` содержит последовательность всех обсервер контейнеров через которые прошло событие от конца к началу.<br>
 * Когда происходит, некоторое событие изменения его `chain` пусто. Затем, при отправке этого события из<br>
 * контейнера, контейнер добавляется в начало списка `chain`. Т.е. контейнер,<br>
 * в котором произошло событие будет в списке последним, а контейнер от которого событие попало адресату первым.<br>
 * `ChaneEvent` Имеет классы наследники соответствующих конкретному виду изменения.<br>
 * [[jmmo.engine.property.Property]] генерирует событие `ChangePropertyEvent`.<br>
 * [[jmmo.engine.property.ObservableContainer]] генерирует события
 * добавления и удаления дочерних обсерверов и указывает себя в качестве узла.<br>
 * Классы наследники `ChangeEvent` могут быть равны только классам того же типа.
 * @param source an `Observable` that has changed
 * @author Tomas Shestakov
*/
sealed abstract class ChangeEvent(override val source: Observable) extends Event(source) {

  /**
   * @return the sequence of `ObservableContainer`'s through the event which was risen in reverse order.
   * Last in sequence is container from which event was risen first.
   * Next container through event was risen will be before last in `chain` sequence, etc.
   * At last container through which the latter took `ChangeEvent` will be last in `chain` sequence.
   */
  def chain: Seq[ObservableContainer]

  /**
   * @return kind of change which be the cause of `ChangeEvent`
   */
  def alteration: (Identifiable.Filter) => Alteration

  /**
   * Copies ChangeEvent with new chain of `ObservableContainer`s.
   * @param containers the sequence of `ObservableContainer`'s through the event which was risen in reverse order.
   * @return new instance of `ChangeEvent`
   */
  def newChain(containers: ObservableContainer*): ChangeEvent
}

object ChangeEvent {

  /**
   * Type of ChangeEvent filter.
   */
  type Filter = (ChangeEvent) => Boolean

  /**
   * Filter which pass all ChangeEvent's
   */
  val passAll = (_: ChangeEvent) => true

  /**
   * Select only events of [[jmmo.engine.property.AddOrRemoveObsEvent]] type
   */
  val addOrRemove = (event: ChangeEvent) => event match {
    case _: AddOrRemoveObsEvent => true
    case _ => false
  }

  /**
   * Select only events of [[jmmo.engine.property.ChangePropertyEvent]] type
   */
  val propertyChange = (event: ChangeEvent) => event match {
      case _: ChangePropertyEvent[_] => true
      case _ => false
    }
}

/**
 * Event of adding or removing `Observable` to/from container.
 * @param source an `Observable` that has changed
 */
abstract class AddOrRemoveObsEvent(source: ObservableContainer) extends ChangeEvent(source) {

  /**
   * @return added or removed to(from) source `Observable`
   */
  def affected: Observable

  /**
   * @return `AddOrRemoveAlteration` which be the cause of `ChangeEvent`
   */
  def alteration: (Identifiable.Filter) => AddOrRemoveAlteration[SerializedObservable]

  /**
   * Copies `AddOrRemoveObsEvent` with new chain of `ObservableContainer`s.
   * @param containers the sequence of `ObservableContainer`'s through the event which was risen in reverse order.
   * @return new instance of `AddOrRemoveObsEvent`
   */
  def newChain(containers: ObservableContainer*): AddOrRemoveObsEvent
}

/**
 * Event of adding `Observable` to container.
 * @param source an `Observable` that has changed
 * @param affected added to source `Observable`
 * @param chain the sequence of `ObservableContainer`'s through the event which was risen
 */
case class AddObsEvent(override val source: ObservableContainer, affected: Observable, chain: ObservableContainer*) extends AddOrRemoveObsEvent(source) {

  /**
   * @return `AddAlteration` which be the cause of `ChangeEvent`
   */
  def alteration = (filter) => AddAlteration(affected.serializedCopy(filter))

  /**
   * Copies `AddObsEvent` with new chain of `ObservableContainer`s.
   * @param containers the sequence of `ObservableContainer`'s through the event which was risen in reverse order.
   * @return new instance of `AddObsEvent`
   */
  def newChain(containers: ObservableContainer*): AddObsEvent = AddObsEvent(source, affected, containers: _*)

  override def toString = "AddObsEvent(" + source + "," + affected + "," + chain.mkString("chain(", ",", ")") + ")"
}

/**
 * Event of removing `Observable` from container
 * @param source an `Observable` that has changed
 * @param affected removed from source `Observable`
 * @param chain the sequence of `ObservableContainer`'s through the event which was risen
 */
case class RemoveObsEvent(override val source: ObservableContainer, affected: Observable, chain: ObservableContainer*) extends AddOrRemoveObsEvent(source) {

  /**
   * @return `RemoveAlteration` which be the cause of `ChangeEvent`
   */
  def alteration = (filter) => RemoveAlteration(affected.serializedCopy(filter))

  /**
   * Copies `RemoveObsEvent` with new chain of `ObservableContainer`s.
   * @param containers the sequence of `ObservableContainer`'s through the event which was risen in reverse order.
   * @return new instance of `RemoveObsEvent`
   */
  def newChain(containers: ObservableContainer*): RemoveObsEvent = RemoveObsEvent(source, affected, containers: _*)

  override def toString = "RemoveObsEvent(" + source + "," + affected + "," + chain.mkString("chain(", ",", ")") + ")"
}

/**
 * Event of changing value of [[jmmo.engine.property.Property]].
 * @param source an `Observable` that has changed
 * @param oldValue previous value of `Property`
 * @param newValue new value of `Property`
 * @param chain the sequence of `ObservableContainer`'s through the event which was risen
 * @tparam A type of `Property`
 */
case class ChangePropertyEvent[A](override val source: Observable, oldValue: A, newValue: A, chain: ObservableContainer*) extends ChangeEvent(source) {

  /**
   * @return `ChangeAlteration` which be the cause of `ChangeEvent`
   */
  def alteration = (filter) => ChangeAlteration(newValue)

  /**
   * Copies `ChangePropertyEvent` with new chain of `ObservableContainer`s.
   * @param containers the sequence of `ObservableContainer`'s through the event which was risen in reverse order.
   * @return new instance of `ChangePropertyEvent`
   */
  def newChain(containers: ObservableContainer*): ChangePropertyEvent[A] = ChangePropertyEvent(source, oldValue, newValue, containers: _*)

  override def toString = "ChangePropertyEvent(" + source + "," + oldValue + "," + newValue + "," + chain.mkString("chain(", ",", ")") + ")"
}
